package com.lt.flashcard.model.service;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Junction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;

import com.lt.flashcard.Utility;
import com.lt.flashcard.model.entity.Flashcard;
import com.lt.flashcard.model.entity.FlashcardSet;
import com.lt.flashcard.model.entity.User;

/**
 * Provide utility to interact with flash card and flash card set
 * 
 * @author ocsen
 * @version 1.0
 * @created 15-Aug-2012 21:38:20
 */
public class FlashcardService extends BasicDAO<Flashcard> {

    public FlashcardService() {
        super(Flashcard.class);
    }

    /**
     * Get all flashcards
     * 
     * @return
     */
    public List<Flashcard> getAllFlashcards() {
        Criteria crit = createCriteria();
        return crit.list();
    }

    /**
     * Get list of flashcard that the user have granted access and not include the current flashcardset
     * 
     * @param user
     * @param flashcardset
     * @return
     */
    public List<Flashcard> getAllowedFlashcard(User user, FlashcardSet flashcardset, String term) {
        if (StringUtils.isBlank(term)) {
            return new ArrayList<Flashcard>();
        }

        UserService usrv = Repository.getInstance().getUserService();
        Criteria crit = createCriteria();
        crit.createAlias("flashcardSet", "fcs");
        // term
        SimpleExpression cterm = Restrictions.like("term", term, MatchMode.START);
        // is author
        SimpleExpression author = Restrictions.eq("fcs.author", user);
        // denied
        Criterion denied = Restrictions.not(Restrictions.like("fcs.deniedusersCache", "," + user.getId() + ","));
        // allow
        SimpleExpression allowed = Restrictions.like("fcs.allowedusersCache", "," + user.getId() + ",");
        // public
        SimpleExpression pl = Restrictions.eq("fcs.privacy", FlashcardSet.PRIVACY_PUBLIC);
        // state
        SimpleExpression sf = Restrictions.eq("fcs.privacy", FlashcardSet.PRIVACY_FRIEND);

        Junction dis = Restrictions.disjunction().add(author).add(denied).add(allowed).add(pl);

        List<User> fl = usrv.getFriendList(user);
        if (fl.size() > 0) {
            dis.add(Restrictions.and(sf, Restrictions.in("fcs.author", fl)));
        }

        if (flashcardset != null && flashcardset.getId() != null) {
            crit.add(Restrictions.conjunction().add(Restrictions.ne("fcs.id", flashcardset.getId())).add(cterm).add(dis));
        } else {
            crit.add(Restrictions.and(cterm, dis));
        }
        crit.add(Restrictions.eq("fcs.admin", FlashcardSet.ADMIN_NO));
        crit.add(Restrictions.eq("fcs.originalflashcardsetCache", ""));
        crit.addOrder(Order.asc("term"));
        crit.addOrder(Order.desc("image"));
        crit.addOrder(Order.desc("soundFront"));
        crit.addOrder(Order.desc("soundBack"));
        crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        crit.setFetchSize(100);
        return crit.list();
    }

    public List<Flashcard> getListFlashcard(String term, FlashcardSet flashcardset) {
        Criteria crit = getCurrentSession().createCriteria(Flashcard.class);
        crit.add(Restrictions.like("term", term, MatchMode.START));

        Session s = getCurrentSession();
        // set != current flashcardset
        String andDiffCurflashcardset = " and fs.id != " + flashcardset.getId();
        // get public flashcard set;
        String queryString = "select fs from FlashcardSet fs where fs.privacy = 0 "
                + (flashcardset.getId() != null ? andDiffCurflashcardset : "");
        Query query = s.createQuery(queryString);
        List<FlashcardSet> publicFlashcardset = query.list();

        // get friend flashcard set-join friendship
        queryString = "select distinct fs " + "from FlashcardSet fs, " + "Friendship fr " + "where " + "fs.privacy = 2 "
                + (flashcardset.getId() != null ? andDiffCurflashcardset : "") + " and ("
                + "(fr.id.user1id = fs.author.id and fr.id.user2id =:condition) " + "or fs.author.id =:condition" + ")";
        query = s.createQuery(queryString);
        query.setParameter("condition", Utility.getUser().getId());
        List<FlashcardSet> friendFlashcardset = query.list();

        // get private flashcard set - join allowedusers and deniedusers table but they aren't exist
        // TODO

        // merge publicFlashcard and friendFlashcardSet
        publicFlashcardset.addAll(friendFlashcardset);
        List<Flashcard> flashcards = new ArrayList<Flashcard>();
        return getFlashcardsFromListFlashcardset(flashcards, publicFlashcardset, term);
    }

    public List<Flashcard> getFlashcardsFromListFlashcardset(List<Flashcard> flashcards, List<FlashcardSet> listFS, String term) {
        for (FlashcardSet flashcardset : listFS) {
            List<Flashcard> tmpFlashcard = getFlashcardsFromFlashcardset(flashcardset.getId(), term);
            if (tmpFlashcard != null) {
                flashcards.addAll(tmpFlashcard);
            }
        }
        return flashcards;
    }

    public List<Flashcard> getFlashcardsFromFlashcardset(Long flashcardsetId, String term) {
        Criteria crit = getCurrentSession().createCriteria(Flashcard.class);
        crit.add(Restrictions.eq("flashcardSet.id", flashcardsetId)).add(Restrictions.like("term", term, MatchMode.START));
        return crit.list();
    }

    public List<Flashcard> getFlashcardsFromFlashcardsetRestful(Long flashcardsetId, String keyword, String sort, String order, int size, int page) {
        Criteria crit = getCurrentSession().createCriteria(Flashcard.class);
        crit.add(Restrictions.eq("flashcardSet.id", flashcardsetId));
        if (StringUtils.isNotBlank(keyword)) {
            crit.add(Restrictions.or(Restrictions.ilike("term", "%" + keyword + "%"), Restrictions.ilike("definition", "%" + keyword + "%")));
        }

        crit.addOrder("asc".equals(order) ? Order.asc(sort) : Order.desc(sort));
        crit.setMaxResults(size);
        crit.setFirstResult(page * size);
        return crit.list();
    }

    /**
     * Update current flashcard
     */
    public void updateFlashcard(Flashcard flashcard, String term, String notes, String definition, String flashcardSoundFront,
            String flashcardSoundBack, FlashcardSet flashcardSet) {
        flashcard.setTerm(term);
        flashcard.setNotes(notes);
        flashcard.setDefinition(definition);
        flashcard.setSoundFront(flashcardSoundFront);
        flashcard.setSoundBack(flashcardSoundBack);
        flashcard.setFlashcardSet(flashcardSet);
    }

    /**
     * Count the number of flashcards of target user
     * 
     * @param user
     * @return
     */
    public int countFlashcardsOfUser(User user) {
        // TODO optimize this, store to user or cache
        Session ses = getCurrentSession();
        Criteria crit = ses.createCriteria(FlashcardSet.class);
        crit.add(Restrictions.eq("author", user));
        crit.setProjection(Projections.sum("totalflashcards"));
        Integer rs = (Integer) crit.uniqueResult();
        return rs == null ? 0 : rs;
    }

    /**
     * check term's flashcard is exits?
     * 
     * @param term
     *            : typing
     * @param setId
     *            : flashcard set id
     * @param originalTerm
     *            : term flashcard
     * @return boolean
     */
    public boolean isExistTerm(String term, Long setId, String originalTerm) {
        Criteria crit = getCurrentSession().createCriteria(Flashcard.class);
        crit.add(Restrictions.eq("term", term));
        crit.add(Restrictions.eq("flashcardSet.id", setId));
        if (crit.list().size() > 0) {
            if (originalTerm != null) {
                if (term.equals(originalTerm)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * check term's flashcard is exits?
     * 
     * @param term
     *            : typing
     * @param setId
     *            : flashcard set id
     * @return boolean: return true when flashcardset is exist term
     */
    public boolean isExistTerm(String term, Long setId) {
        Criteria crit = getCurrentSession().createCriteria(Flashcard.class);
        crit.add(Restrictions.eq("term", term));
        crit.add(Restrictions.eq("flashcardSet.id", setId));
        if (crit.list().size() > 0) {
            return true;
        }
        return false;
    }

    /**
     * Override SAVE function and delete basicDAO to fix error: A different object with same identifier was already
     * associated with the session
     */
    @Override
    public boolean save(Flashcard flashcard) {
        // Check for required field
        if (flashcard.getTerm().length() == 0 || flashcard.getFlashcardSet() == null
                || (flashcard.getDefinition().length() == 0 && flashcard.getImage().length() == 0)) {
            return false;
        }

        // Check for duplicate term
        if (flashcard.getId() == null) {
            for (Flashcard fc : flashcard.getFlashcardSet().getFlashcards()) {
                if (fc.getTerm().toLowerCase().equals(flashcard.getTerm().toLowerCase())) {
                    return false;
                }
            }
        } else {
            for (Flashcard fc : flashcard.getFlashcardSet().getFlashcards()) {
                if (fc.getTerm().toLowerCase().equals(flashcard.getTerm().toLowerCase()) && !fc.getId().equals(flashcard.getId())) {
                    return false;
                }
            }
        }

        if (flashcard.getId() != null) {
            getCurrentSession().merge(flashcard);
        } else { // new
            getCurrentSession().saveOrUpdate(flashcard);
        }
        return true;
    }

    @Override
    public void delete(Flashcard flashcard) {
        if (flashcard == null) {
            return;
        }
        // delete the image
        // Repository.getInstance().deleteBinaryData(flashcard.getImage());
        // TODO delete sound
        // TODO check in case delete multiple card one time
        if (flashcard.getId() != null) {
            getCurrentSession().clear();
            getCurrentSession().delete(flashcard);
        } else {
            getCurrentSession().delete(flashcard);
        }

        if (flashcard.getFlashcardSet().getAdmin() != FlashcardSet.ADMIN_YES) {
            // update total flashcard set
            User author = flashcard.getFlashcardSet().getAuthor();
            author.setCurrentblankflashcard(author.getCurrentblankflashcard() + 1);
            Repository.getInstance().getUserService().save(author);
        }
    }

    public List<Flashcard> filterFlashcard(String term, Long fcsid) {
        Criteria crit = getCurrentSession().createCriteria(Flashcard.class);
        crit.add(Restrictions.eq("flashcardSet.id", fcsid));
        crit.add(Restrictions.like("term", term, MatchMode.ANYWHERE));
        List<Flashcard> list = crit.list();
        return list;
    }
}